"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createSubset = void 0;
var _is = require("../../utils/is.js");
var _object = require("../../utils/object.js");
var _array = require("../../utils/array.js");
var _customs = require("../../utils/customs.js");
var _DimensionError = require("../../error/DimensionError.js");
var _factory = require("../../utils/factory.js");
const name = 'subset';
const dependencies = ['typed', 'matrix', 'zeros', 'add'];
const createSubset = exports.createSubset = /* #__PURE__ */(0, _factory.factory)(name, dependencies, _ref => {
  let {
    typed,
    matrix,
    zeros,
    add
  } = _ref;
  /**
   * Get or set a subset of a matrix or string.
   *
   * Syntax:
   *     math.subset(value, index)                                // retrieve a subset
   *     math.subset(value, index, replacement [, defaultValue])  // replace a subset
   *
   * Examples:
   *
   *     // get a subset
   *     const d = [[1, 2], [3, 4]]
   *     math.subset(d, math.index(1, 0))             // returns 3
   *     math.subset(d, math.index([0, 1], 1))        // returns [[2], [4]]
   *     math.subset(d, math.index([false, true], 0)) // returns [[3]]
   *
   *     // replace a subset
   *     const e = []
   *     const f = math.subset(e, math.index(0, [0, 2]), [5, 6])  // f = [[5, 0, 6]]
   *     const g = math.subset(f, math.index(1, 1), 7, 0)         // g = [[5, 0, 6], [0, 7, 0]]
   *     math.subset(g, math.index([false, true], 1), 8)          // returns [[5, 0, 6], [0, 8, 0]]
   *
   *     // get submatrix using ranges
   *     const M = [
   *       [1,2,3],
   *       [4,5,6],
   *       [7,8,9]
   *     ]
   *     math.subset(M, math.index(math.range(0,2), math.range(0,3))) // [[1, 2, 3], [4, 5, 6]]
   *
   * See also:
   *
   *     size, resize, squeeze, index
   *
   * @param {Array | Matrix | string} matrix  An array, matrix, or string
   * @param {Index} index
   *    For each dimension of the target, specifies an index or a list of
   *    indices to fetch or set. `subset` uses the cartesian product of
   *    the indices specified in each dimension.
   * @param {*} [replacement]                 An array, matrix, or scalar.
   *                                          If provided, the subset is replaced with replacement.
   *                                          If not provided, the subset is returned
   * @param {*} [defaultValue=undefined]      Default value, filled in on new entries when
   *                                          the matrix is resized. If not provided,
   *                                          math.matrix elements will be left undefined.
   * @return {Array | Matrix | string} Either the retrieved subset or the updated matrix.
   */

  return typed(name, {
    // get subset
    'Matrix, Index': function (value, index) {
      if ((0, _array.isEmptyIndex)(index)) {
        return matrix();
      }
      (0, _array.validateIndexSourceSize)(value, index);
      return value.subset(index);
    },
    'Array, Index': typed.referTo('Matrix, Index', function (subsetRef) {
      return function (value, index) {
        const subsetResult = subsetRef(matrix(value), index);
        return index.isScalar() ? subsetResult : subsetResult.valueOf();
      };
    }),
    'Object, Index': _getObjectProperty,
    'string, Index': _getSubstring,
    // set subset
    'Matrix, Index, any, any': function (value, index, replacement, defaultValue) {
      if ((0, _array.isEmptyIndex)(index)) {
        return value;
      }
      (0, _array.validateIndexSourceSize)(value, index);
      return value.clone().subset(index, _broadcastReplacement(replacement, index), defaultValue);
    },
    'Array, Index, any, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) {
      return function (value, index, replacement, defaultValue) {
        const subsetResult = subsetRef(matrix(value), index, replacement, defaultValue);
        return subsetResult.isMatrix ? subsetResult.valueOf() : subsetResult;
      };
    }),
    'Array, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) {
      return function (value, index, replacement) {
        return subsetRef(matrix(value), index, replacement, undefined).valueOf();
      };
    }),
    'Matrix, Index, any': typed.referTo('Matrix, Index, any, any', function (subsetRef) {
      return function (value, index, replacement) {
        return subsetRef(value, index, replacement, undefined);
      };
    }),
    'string, Index, string': _setSubstring,
    'string, Index, string, string': _setSubstring,
    'Object, Index, any': _setObjectProperty
  });

  /**
   * Broadcasts a replacment value to be the same size as index
   * @param {number | BigNumber | Array | Matrix} replacement Replacement value to try to broadcast
   * @param {*} index Index value
   * @returns broadcasted replacement that matches the size of index
   */

  function _broadcastReplacement(replacement, index) {
    if (typeof replacement === 'string') {
      throw new Error('can\'t boradcast a string');
    }
    if (index._isScalar) {
      return replacement;
    }
    const indexSize = index.size();
    if (indexSize.every(d => d > 0)) {
      try {
        return add(replacement, zeros(indexSize));
      } catch (error) {
        return replacement;
      }
    } else {
      return replacement;
    }
  }
});

/**
 * Retrieve a subset of a string
 * @param {string} str            string from which to get a substring
 * @param {Index} index           An index or list of indices (character positions)
 * @returns {string} substring
 * @private
 */
function _getSubstring(str, index) {
  if (!(0, _is.isIndex)(index)) {
    // TODO: better error message
    throw new TypeError('Index expected');
  }
  if ((0, _array.isEmptyIndex)(index)) {
    return '';
  }
  (0, _array.validateIndexSourceSize)(Array.from(str), index);
  if (index.size().length !== 1) {
    throw new _DimensionError.DimensionError(index.size().length, 1);
  }

  // validate whether the range is out of range
  const strLen = str.length;
  (0, _array.validateIndex)(index.min()[0], strLen);
  (0, _array.validateIndex)(index.max()[0], strLen);
  const range = index.dimension(0);
  let substr = '';
  range.forEach(function (v) {
    substr += str.charAt(v);
  });
  return substr;
}

/**
 * Replace a substring in a string
 * @param {string} str            string to be replaced
 * @param {Index} index           An index or list of indices (character positions)
 * @param {string} replacement    Replacement string
 * @param {string} [defaultValue] Default value to be used when resizing
 *                                the string. is ' ' by default
 * @returns {string} result
 * @private
 */
function _setSubstring(str, index, replacement, defaultValue) {
  if (!index || index.isIndex !== true) {
    // TODO: better error message
    throw new TypeError('Index expected');
  }
  if ((0, _array.isEmptyIndex)(index)) {
    return str;
  }
  (0, _array.validateIndexSourceSize)(Array.from(str), index);
  if (index.size().length !== 1) {
    throw new _DimensionError.DimensionError(index.size().length, 1);
  }
  if (defaultValue !== undefined) {
    if (typeof defaultValue !== 'string' || defaultValue.length !== 1) {
      throw new TypeError('Single character expected as defaultValue');
    }
  } else {
    defaultValue = ' ';
  }
  const range = index.dimension(0);
  const len = range.size()[0];
  if (len !== replacement.length) {
    throw new _DimensionError.DimensionError(range.size()[0], replacement.length);
  }

  // validate whether the range is out of range
  const strLen = str.length;
  (0, _array.validateIndex)(index.min()[0]);
  (0, _array.validateIndex)(index.max()[0]);

  // copy the string into an array with characters
  const chars = [];
  for (let i = 0; i < strLen; i++) {
    chars[i] = str.charAt(i);
  }
  range.forEach(function (v, i) {
    chars[v] = replacement.charAt(i[0]);
  });

  // initialize undefined characters with a space
  if (chars.length > strLen) {
    for (let i = strLen - 1, len = chars.length; i < len; i++) {
      if (!chars[i]) {
        chars[i] = defaultValue;
      }
    }
  }
  return chars.join('');
}

/**
 * Retrieve a property from an object
 * @param {Object} object
 * @param {Index} index
 * @return {*} Returns the value of the property
 * @private
 */
function _getObjectProperty(object, index) {
  if ((0, _array.isEmptyIndex)(index)) {
    return undefined;
  }
  if (index.size().length !== 1) {
    throw new _DimensionError.DimensionError(index.size(), 1);
  }
  const key = index.dimension(0);
  if (typeof key !== 'string') {
    throw new TypeError('String expected as index to retrieve an object property');
  }
  return (0, _customs.getSafeProperty)(object, key);
}

/**
 * Set a property on an object
 * @param {Object} object
 * @param {Index} index
 * @param {*} replacement
 * @return {*} Returns the updated object
 * @private
 */
function _setObjectProperty(object, index, replacement) {
  if ((0, _array.isEmptyIndex)(index)) {
    return object;
  }
  if (index.size().length !== 1) {
    throw new _DimensionError.DimensionError(index.size(), 1);
  }
  const key = index.dimension(0);
  if (typeof key !== 'string') {
    throw new TypeError('String expected as index to retrieve an object property');
  }

  // clone the object, and apply the property to the clone
  const updated = (0, _object.clone)(object);
  (0, _customs.setSafeProperty)(updated, key, replacement);
  return updated;
}